home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 May / maximum-cd-2009-05.iso / DiscContents / Firefox Setup 3.0.6.exe / nonlocalized / components / nsUrlClassifierListManager.js < prev    next >
Encoding:
Text File  |  2009-01-19  |  19.5 KB  |  612 lines

  1. //@line 37 "e:\fx19rel\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\src\nsUrlClassifierListManager.js"
  2.  
  3. const Cc = Components.classes;
  4. const Ci = Components.interfaces;
  5.  
  6. //@line 37 "e:\fx19rel\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\content\listmanager.js"
  7.  
  8.  
  9. // A class that manages lists, namely white and black lists for
  10. // phishing or malware protection. The ListManager knows how to fetch,
  11. // update, and store lists.
  12. //
  13. // There is a single listmanager for the whole application.
  14. //
  15. // TODO more comprehensive update tests, for example add unittest check 
  16. //      that the listmanagers tables are properly written on updates
  17.  
  18. // How frequently we check for updates (30 minutes)
  19. const kUpdateInterval = 30 * 60 * 1000;
  20.  
  21. function QueryAdapter(callback) {
  22.   this.callback_ = callback;
  23. };
  24.  
  25. QueryAdapter.prototype.handleResponse = function(value) {
  26.   this.callback_.handleEvent(value);
  27. }
  28.  
  29. /**
  30.  * A ListManager keeps track of black and white lists and knows
  31.  * how to update them.
  32.  *
  33.  * @constructor
  34.  */
  35. function PROT_ListManager() {
  36.   this.debugZone = "listmanager";
  37.   G_debugService.enableZone(this.debugZone);
  38.  
  39.   this.currentUpdateChecker_ = null;   // set when we toggle updates
  40.   this.prefs_ = new G_Preferences();
  41.  
  42.   this.updateserverURL_ = null;
  43.   this.gethashURL_ = null;
  44.  
  45.   this.isTesting_ = false;
  46.  
  47.   this.tablesData = {};
  48.  
  49.   this.observerServiceObserver_ = new G_ObserverServiceObserver(
  50.                                           'xpcom-shutdown',
  51.                                           BindToObject(this.shutdown_, this),
  52.                                           true /*only once*/);
  53.  
  54.   // Lazily create the key manager (to avoid fetching keys when they
  55.   // aren't needed).
  56.   this.keyManager_ = null;
  57.  
  58.   this.rekeyObserver_ = new G_ObserverServiceObserver(
  59.                                           'url-classifier-rekey-requested',
  60.                                           BindToObject(this.rekey_, this),
  61.                                           false);
  62.   this.updateWaitingForKey_ = false;
  63.  
  64.   this.cookieObserver_ = new G_ObserverServiceObserver(
  65.                                           'cookie-changed',
  66.                                           BindToObject(this.cookieChanged_, this),
  67.                                           false);
  68.  
  69.   /* Backoff interval should be between 30 and 60 minutes. */
  70.   var backoffInterval = 30 * 60 * 1000;
  71.   backoffInterval += Math.floor(Math.random() * (30 * 60 * 1000));
  72.  
  73.   this.requestBackoff_ = new RequestBackoff(2 /* max errors */,
  74.                                       60*1000 /* retry interval, 1 min */,
  75.                                             4 /* num requests */,
  76.                                    60*60*1000 /* request time, 60 min */,
  77.                               backoffInterval /* backoff interval, 60 min */,
  78.                                  8*60*60*1000 /* max backoff, 8hr */);
  79.  
  80.   this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
  81.                    .getService(Ci.nsIUrlClassifierDBService);
  82.  
  83.   this.hashCompleter_ = Cc["@mozilla.org/url-classifier/hashcompleter;1"]
  84.                         .getService(Ci.nsIUrlClassifierHashCompleter);
  85. }
  86.  
  87. /**
  88.  * xpcom-shutdown callback
  89.  * Delete all of our data tables which seem to leak otherwise.
  90.  */
  91. PROT_ListManager.prototype.shutdown_ = function() {
  92.   for (var name in this.tablesData) {
  93.     delete this.tablesData[name];
  94.   }
  95. }
  96.  
  97. /**
  98.  * Set the url we check for updates.  If the new url is valid and different,
  99.  * update our table list.
  100.  * 
  101.  * After setting the update url, the caller is responsible for registering
  102.  * tables and then toggling update checking.  All the code for this logic is
  103.  * currently in browser/components/safebrowsing.  Maybe it should be part of
  104.  * the listmanger?
  105.  */
  106. PROT_ListManager.prototype.setUpdateUrl = function(url) {
  107.   G_Debug(this, "Set update url: " + url);
  108.   if (url != this.updateserverURL_) {
  109.     this.updateserverURL_ = url;
  110.     this.requestBackoff_.reset();
  111.     
  112.     // Remove old tables which probably aren't valid for the new provider.
  113.     for (var name in this.tablesData) {
  114.       delete this.tablesData[name];
  115.     }
  116.   }
  117. }
  118.  
  119. /**
  120.  * Set the gethash url.
  121.  */
  122. PROT_ListManager.prototype.setGethashUrl = function(url) {
  123.   G_Debug(this, "Set gethash url: " + url);
  124.   if (url != this.gethashURL_) {
  125.     this.gethashURL_ = url;
  126.     this.hashCompleter_.gethashUrl = url;
  127.   }
  128. }
  129.  
  130. /**
  131.  * Set the crypto key url.
  132.  * @param url String
  133.  */
  134. PROT_ListManager.prototype.setKeyUrl = function(url) {
  135.   G_Debug(this, "Set key url: " + url);
  136.   if (!this.keyManager_) {
  137.     this.keyManager_ = new PROT_UrlCryptoKeyManager();
  138.     this.keyManager_.onNewKey(BindToObject(this.newKey_, this));
  139.  
  140.     this.hashCompleter_.setKeys(this.keyManager_.getClientKey(),
  141.                                 this.keyManager_.getWrappedKey());
  142.   }
  143.  
  144.   this.keyManager_.setKeyUrl(url);
  145. }
  146.  
  147. /**
  148.  * Register a new table table
  149.  * @param tableName - the name of the table
  150.  * @param opt_requireMac true if a mac is required on update, false otherwise
  151.  * @returns true if the table could be created; false otherwise
  152.  */
  153. PROT_ListManager.prototype.registerTable = function(tableName, 
  154.                                                     opt_requireMac) {
  155.   this.tablesData[tableName] = {};
  156.   this.tablesData[tableName].needsUpdate = false;
  157.  
  158.   return true;
  159. }
  160.  
  161. /**
  162.  * Enable updates for some tables
  163.  * @param tables - an array of table names that need updating
  164.  */
  165. PROT_ListManager.prototype.enableUpdate = function(tableName) {
  166.   var changed = false;
  167.   var table = this.tablesData[tableName];
  168.   if (table) {
  169.     G_Debug(this, "Enabling table updates for " + tableName);
  170.     table.needsUpdate = true;
  171.     changed = true;
  172.   }
  173.  
  174.   if (changed === true)
  175.     this.maybeToggleUpdateChecking();
  176. }
  177.  
  178. /**
  179.  * Disables updates for some tables
  180.  * @param tables - an array of table names that no longer need updating
  181.  */
  182. PROT_ListManager.prototype.disableUpdate = function(tableName) {
  183.   var changed = false;
  184.   var table = this.tablesData[tableName];
  185.   if (table) {
  186.     G_Debug(this, "Disabling table updates for " + tableName);
  187.     table.needsUpdate = false;
  188.     changed = true;
  189.   }
  190.  
  191.   if (changed === true)
  192.     this.maybeToggleUpdateChecking();
  193. }
  194.  
  195. /**
  196.  * Determine if we have some tables that need updating.
  197.  */
  198. PROT_ListManager.prototype.requireTableUpdates = function() {
  199.   for (var type in this.tablesData) {
  200.     // Tables that need updating even if other tables dont require it
  201.     if (this.tablesData[type].needsUpdate)
  202.       return true;
  203.   }
  204.  
  205.   return false;
  206. }
  207.  
  208. /**
  209.  * Start managing the lists we know about. We don't do this automatically
  210.  * when the listmanager is instantiated because their profile directory
  211.  * (where we store the lists) might not be available.
  212.  */
  213. PROT_ListManager.prototype.maybeStartManagingUpdates = function() {
  214.   if (this.isTesting_)
  215.     return;
  216.  
  217.   // We might have been told about tables already, so see if we should be
  218.   // actually updating.
  219.   this.maybeToggleUpdateChecking();
  220. }
  221.  
  222. PROT_ListManager.prototype.kickoffUpdate_ = function (tableData)
  223. {
  224.   this.startingUpdate_ = false;
  225.   // If the user has never downloaded tables, do the check now.
  226.   // If the user has tables, add a fuzz of a few minutes.
  227.   var initialUpdateDelay = 3000;
  228.   if (tableData != "") {
  229.     // Add a fuzz of 0-5 minutes.
  230.     initialUpdateDelay += Math.floor(Math.random() * (5 * 60 * 1000));
  231.   }
  232.  
  233.   this.currentUpdateChecker_ =
  234.     new G_Alarm(BindToObject(this.checkForUpdates, this),
  235.                 initialUpdateDelay);
  236. }
  237.  
  238. /**
  239.  * Determine if we have any tables that require updating.  Different
  240.  * Wardens may call us with new tables that need to be updated.
  241.  */ 
  242. PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
  243.   // If we are testing or dont have an application directory yet, we should
  244.   // not start reading tables from disk or schedule remote updates
  245.   if (this.isTesting_)
  246.     return;
  247.  
  248.   // We update tables if we have some tables that want updates.  If there
  249.   // are no tables that want to be updated - we dont need to check anything.
  250.   if (this.requireTableUpdates() === true) {
  251.     G_Debug(this, "Starting managing lists");
  252.     this.startUpdateChecker();
  253.  
  254.     // Multiple warden can ask us to reenable updates at the same time, but we
  255.     // really just need to schedule a single update.
  256.     if (!this.currentUpdateChecker && !this.startingUpdate_) {
  257.       this.startingUpdate_ = true;
  258.       // check the current state of tables in the database
  259.       this.dbService_.getTables(BindToObject(this.kickoffUpdate_, this));
  260.     }
  261.   } else {
  262.     G_Debug(this, "Stopping managing lists (if currently active)");
  263.     this.stopUpdateChecker();                    // Cancel pending updates
  264.   }
  265. }
  266.  
  267. /**
  268.  * Start periodic checks for updates. Idempotent.
  269.  * We want to distribute update checks evenly across the update period (an
  270.  * hour).  To do this, we pick a random number of time between 0 and 30
  271.  * minutes.  The client first checks at 15 + rand, then every 30 minutes after
  272.  * that.
  273.  */
  274. PROT_ListManager.prototype.startUpdateChecker = function() {
  275.   this.stopUpdateChecker();
  276.   
  277.   // Schedule the first check for between 15 and 45 minutes.
  278.   var repeatingUpdateDelay = kUpdateInterval / 2;
  279.   repeatingUpdateDelay += Math.floor(Math.random() * kUpdateInterval);
  280.   this.updateChecker_ = new G_Alarm(BindToObject(this.initialUpdateCheck_,
  281.                                                  this),
  282.                                     repeatingUpdateDelay);
  283. }
  284.  
  285. /**
  286.  * Callback for the first update check.
  287.  * We go ahead and check for table updates, then start a regular timer (once
  288.  * every 30 minutes).
  289.  */
  290. PROT_ListManager.prototype.initialUpdateCheck_ = function() {
  291.   this.checkForUpdates();
  292.   this.updateChecker_ = new G_Alarm(BindToObject(this.checkForUpdates, this), 
  293.                                     kUpdateInterval, true /* repeat */);
  294. }
  295.  
  296. /**
  297.  * Stop checking for updates. Idempotent.
  298.  */
  299. PROT_ListManager.prototype.stopUpdateChecker = function() {
  300.   if (this.updateChecker_) {
  301.     this.updateChecker_.cancel();
  302.     this.updateChecker_ = null;
  303.   }
  304.   // Cancel the oneoff check from maybeToggleUpdateChecking.
  305.   if (this.currentUpdateChecker_) {
  306.     this.currentUpdateChecker_.cancel();
  307.     this.currentUpdateChecker_ = null;
  308.   }
  309. }
  310.  
  311. /**
  312.  * Provides an exception free way to look up the data in a table. We
  313.  * use this because at certain points our tables might not be loaded,
  314.  * and querying them could throw.
  315.  *
  316.  * @param table String Name of the table that we want to consult
  317.  * @param key String Key for table lookup
  318.  * @param callback nsIUrlListManagerCallback (ie., Function) given false or the
  319.  *        value in the table corresponding to key.  If the table name does not
  320.  *        exist, we return false, too.
  321.  */
  322. PROT_ListManager.prototype.safeLookup = function(key, callback) {
  323.   try {
  324.     G_Debug(this, "safeLookup: " + key);
  325.     var cb = new QueryAdapter(callback);
  326.     this.dbService_.lookup(key,
  327.                            BindToObject(cb.handleResponse, cb),
  328.                            true);
  329.   } catch(e) {
  330.     G_Debug(this, "safeLookup masked failure for key " + key + ": " + e);
  331.     callback.handleEvent("");
  332.   }
  333. }
  334.  
  335. /**
  336.  * Updates our internal tables from the update server
  337.  *
  338.  * @returns true when a new request was scheduled, false if an old request
  339.  *          was still pending.
  340.  */
  341. PROT_ListManager.prototype.checkForUpdates = function() {
  342.   // Allow new updates to be scheduled from maybeToggleUpdateChecking()
  343.   this.currentUpdateChecker_ = null;
  344.  
  345.   if (!this.updateserverURL_) {
  346.     G_Debug(this, 'checkForUpdates: no update server url');
  347.     return false;
  348.   }
  349.  
  350.   // See if we've triggered the request backoff logic.
  351.   if (!this.requestBackoff_.canMakeRequest())
  352.     return false;
  353.  
  354.   // Grab the current state of the tables from the database
  355.   this.dbService_.getTables(BindToObject(this.makeUpdateRequest_, this));
  356.   return true;
  357. }
  358.  
  359. /**
  360.  * Method that fires the actual HTTP update request.
  361.  * First we reset any tables that have disappeared.
  362.  * @param tableData List of table data already in the database, in the form
  363.  *        tablename;<chunk ranges>\n
  364.  */
  365. PROT_ListManager.prototype.makeUpdateRequest_ = function(tableData) {
  366.   if (!this.keyManager_)
  367.     return;
  368.  
  369.   if (!this.keyManager_.hasKey()) {
  370.     // We don't have a client key yet.  Schedule a rekey, and rerequest
  371.     // when we have one.
  372.  
  373.     // If there's already an update waiting for a new key, don't bother.
  374.     if (this.updateWaitingForKey_)
  375.       return;
  376.  
  377.     // If maybeReKey() returns false we have asked for too many keys,
  378.     // and won't be getting a new one.  Since we don't want to do
  379.     // updates without a client key, we'll skip this update if maybeReKey()
  380.     // fails.
  381.     if (this.keyManager_.maybeReKey())
  382.       this.updateWaitingForKey_ = true;
  383.  
  384.     return;
  385.   }
  386.  
  387.   var tableList;
  388.   var tableNames = {};
  389.   for (var tableName in this.tablesData) {
  390.     if (this.tablesData[tableName].needsUpdate)
  391.       tableNames[tableName] = true;
  392.     if (!tableList) {
  393.       tableList = tableName;
  394.     } else {
  395.       tableList += "," + tableName;
  396.     }
  397.   }
  398.  
  399.   var request = "";
  400.  
  401.   // For each table already in the database, include the chunk data from
  402.   // the database
  403.   var lines = tableData.split("\n");
  404.   for (var i = 0; i < lines.length; i++) {
  405.     var fields = lines[i].split(";");
  406.     if (tableNames[fields[0]]) {
  407.       request += lines[i] + ":mac\n";
  408.       delete tableNames[fields[0]];
  409.     }
  410.   }
  411.  
  412.   // For each requested table that didn't have chunk data in the database,
  413.   // request it fresh
  414.   for (var tableName in tableNames) {
  415.     request += tableName + ";:mac\n";
  416.   }
  417.  
  418.   G_Debug(this, 'checkForUpdates: scheduling request..');
  419.   var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
  420.                  .getService(Ci.nsIUrlClassifierStreamUpdater);
  421.   try {
  422.     streamer.updateUrl = this.updateserverURL_ +
  423.                          "&wrkey=" + this.keyManager_.getWrappedKey();
  424.   } catch (e) {
  425.     G_Debug(this, 'invalid url');
  426.     return;
  427.   }
  428.  
  429.   this.requestBackoff_.noteRequest();
  430.  
  431.   if (!streamer.downloadUpdates(tableList,
  432.                                 request,
  433.                                 this.keyManager_.getClientKey(),
  434.                                 BindToObject(this.updateSuccess_, this),
  435.                                 BindToObject(this.updateError_, this),
  436.                                 BindToObject(this.downloadError_, this))) {
  437.     G_Debug(this, "pending update, wait until later");
  438.   }
  439. }
  440.  
  441. /**
  442.  * Callback function if the update request succeeded.
  443.  * @param waitForUpdate String The number of seconds that the client should
  444.  *        wait before requesting again.
  445.  */
  446. PROT_ListManager.prototype.updateSuccess_ = function(waitForUpdate) {
  447.   G_Debug(this, "update success: " + waitForUpdate);
  448.   if (waitForUpdate) {
  449.     var delay = parseInt(waitForUpdate, 10);
  450.     // As long as the delay is something sane (5 minutes or more), update
  451.     // our delay time for requesting updates
  452.     if (delay >= (5 * 60) && this.updateChecker_)
  453.       this.updateChecker_.setDelay(delay * 1000);
  454.   }
  455.  
  456.   // Let the backoff object know that we completed successfully.
  457.   this.requestBackoff_.noteServerResponse(200);
  458. }
  459.  
  460. /**
  461.  * Callback function if the update request succeeded.
  462.  * @param result String The error code of the failure
  463.  */
  464. PROT_ListManager.prototype.updateError_ = function(result) {
  465.   G_Debug(this, "update error: " + result);
  466.   // XXX: there was some trouble applying the updates.
  467. }
  468.  
  469. /**
  470.  * Callback function when the download failed
  471.  * @param status String http status or an empty string if connection refused.
  472.  */
  473. PROT_ListManager.prototype.downloadError_ = function(status) {
  474.   G_Debug(this, "download error: " + status);
  475.   // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
  476.   // error.  In this case, we treat this is a http 500 error.
  477.   if (!status) {
  478.     status = 500;
  479.   }
  480.   status = parseInt(status, 10);
  481.   this.requestBackoff_.noteServerResponse(status);
  482.  
  483.   if (this.requestBackoff_.isErrorStatus(status)) {
  484.     // Schedule an update for when our backoff is complete
  485.     this.currentUpdateChecker_ =
  486.       new G_Alarm(BindToObject(this.checkForUpdates, this),
  487.                   this.requestBackoff_.nextRequestDelay());
  488.   }
  489. }
  490.  
  491. /**
  492.  * Called when either the update process or a gethash request signals
  493.  * that the server requested a rekey.
  494.  */
  495. PROT_ListManager.prototype.rekey_ = function() {
  496.   G_Debug(this, "rekey requested");
  497.  
  498.   // The current key is no good anymore.
  499.   this.keyManager_.dropKey();
  500.   this.keyManager_.maybeReKey();
  501. }
  502.  
  503. /**
  504.  * Called when cookies are cleared - clears the current MAC keys.
  505.  */
  506. PROT_ListManager.prototype.cookieChanged_ = function(subject, topic, data) {
  507.   if (data != "cleared")
  508.     return;
  509.  
  510.   G_Debug(this, "cookies cleared");
  511.   this.keyManager_.dropKey();
  512. }
  513.  
  514. /**
  515.  * Called when we've received a new key from the server.
  516.  */
  517. PROT_ListManager.prototype.newKey_ = function() {
  518.   G_Debug(this, "got a new MAC key");
  519.  
  520.   this.hashCompleter_.setKeys(this.keyManager_.getClientKey(),
  521.                               this.keyManager_.getWrappedKey());
  522.  
  523.   if (this.keyManager_.hasKey()) {
  524.     if (this.updateWaitingForKey_) {
  525.       this.updateWaitingForKey_ = false;
  526.       this.checkForUpdates();
  527.     }
  528.   }
  529. }
  530.  
  531. PROT_ListManager.prototype.QueryInterface = function(iid) {
  532.   if (iid.equals(Ci.nsISupports) ||
  533.       iid.equals(Ci.nsIUrlListManager) ||
  534.       iid.equals(Ci.nsITimerCallback))
  535.     return this;
  536.  
  537.   Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  538.   return null;
  539. }
  540. //@line 42 "e:\fx19rel\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\src\nsUrlClassifierListManager.js"
  541.  
  542. var modScope = this;
  543. function Init() {
  544.   // Pull the library in.
  545.   var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
  546.               .getService().wrappedJSObject;
  547.   Function.prototype.inherits = jslib.Function.prototype.inherits;
  548.   modScope.G_Preferences = jslib.G_Preferences;
  549.   modScope.G_PreferenceObserver = jslib.G_PreferenceObserver;
  550.   modScope.G_ObserverServiceObserver = jslib.G_ObserverServiceObserver;
  551.   modScope.G_Debug = jslib.G_Debug;
  552.   modScope.G_Assert = jslib.G_Assert;
  553.   modScope.G_debugService = jslib.G_debugService;
  554.   modScope.G_Alarm = jslib.G_Alarm;
  555.   modScope.BindToObject = jslib.BindToObject;
  556.   modScope.PROT_XMLFetcher = jslib.PROT_XMLFetcher;
  557.   modScope.PROT_UrlCryptoKeyManager = jslib.PROT_UrlCryptoKeyManager;
  558.   modScope.RequestBackoff = jslib.RequestBackoff;
  559.  
  560.   // We only need to call Init once.
  561.   modScope.Init = function() {};
  562. }
  563.  
  564. // Module object
  565. function UrlClassifierListManagerMod() {
  566.   this.firstTime = true;
  567.   this.cid = Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}");
  568.   this.progid = "@mozilla.org/url-classifier/listmanager;1";
  569. }
  570.  
  571. UrlClassifierListManagerMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) {
  572.   if (this.firstTime) {
  573.     this.firstTime = false;
  574.     throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  575.   }
  576.   compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  577.   compMgr.registerFactoryLocation(this.cid,
  578.                                   "UrlClassifier List Manager Module",
  579.                                   this.progid,
  580.                                   fileSpec,
  581.                                   loc,
  582.                                   type);
  583. };
  584.  
  585. UrlClassifierListManagerMod.prototype.getClassObject = function(compMgr, cid, iid) {  
  586.   if (!cid.equals(this.cid))
  587.     throw Components.results.NS_ERROR_NO_INTERFACE;
  588.   if (!iid.equals(Ci.nsIFactory))
  589.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  590.  
  591.   return this.factory;
  592. }
  593.  
  594. UrlClassifierListManagerMod.prototype.canUnload = function(compMgr) {
  595.   return true;
  596. }
  597.  
  598. UrlClassifierListManagerMod.prototype.factory = {
  599.   createInstance: function(outer, iid) {
  600.     if (outer != null)
  601.       throw Components.results.NS_ERROR_NO_AGGREGATION;
  602.     Init();
  603.     return (new PROT_ListManager()).QueryInterface(iid);
  604.   }
  605. };
  606.  
  607. var ListManagerModInst = new UrlClassifierListManagerMod();
  608.  
  609. function NSGetModule(compMgr, fileSpec) {
  610.   return ListManagerModInst;
  611. }
  612.